#include "MeuAgentePrincipal.h"
#include <AgentePrincipal.h>
#include <Windows.h>
#include <BWAPI\Position.h>
#include <BWAPI\UnitType.h>
#include <map>
#include <algorithm>
#include "MineChooser.h"
#include "PlaceChooser.h"
using namespace std;


#define SETI set<Unidade*>::iterator

#define NEXUS BWAPI::UnitTypes::Protoss_Nexus
#define PYLON BWAPI::UnitTypes::Protoss_Pylon
#define FORGE BWAPI::UnitTypes::Protoss_Forge
#define GATEWAY BWAPI::UnitTypes::Protoss_Gateway
#define ASSIMILATOR BWAPI::UnitTypes::Protoss_Assimilator

#define PROBE BWAPI::UnitTypes::Protoss_Probe
#define ZEALOT BWAPI::UnitTypes::Protoss_Zealot
#define DRAGOON BWAPI::UnitTypes::Protoss_Dragoon

#define PYLON_MINERAL_COST 100
#define FORGE_MINERAL_COST 150
#define GATEWAY_MINERAL_COST 150
#define ASSIMILATOR_MINERAL_COST 100

#define UPGRADE_PLASMA_SHIELDS BWAPI::UpgradeTypes::Protoss_Plasma_Shields

#define PROBES_LEGION_SIZE 20
#define GAS_COLLECTORS_MAX 3
#define RAIO_MINERACAO 900
PlaceChooser PlaceChooser;

void NewThread(LPTHREAD_START_ROUTINE Function, LPVOID Parameter){
	CreateThread(NULL,0,Function,Parameter,0,NULL);
}


//blackboard!
bool GameOver = false;
Unidade *base = NULL;

bool Sparta = false;
set<Unidade*> *grupoTaticoAtual;
set<set<Unidade*>*> gruposTaticos;
map<set<Unidade*>*,bool> emApuros;
//

#define DEBUG true

int contarUnidadesDoTipo(std::set<Unidade*> lista, BWAPI::UnitType tipo);

HANDLE teste;
void print(char *p){
	if(DEBUG) WriteConsoleA(teste, p, strlen(p), NULL, NULL);
}



std::pair<Unidade*, double> getUnidadeMaisProxima(Unidade* u, std::set<Unidade*> lista) {
	double distance = 999999;
	Unidade* perto = NULL;
	if(u == NULL) return std::make_pair(perto, distance);
	for(std::set<Unidade*>::iterator it = lista.begin(); it != lista.end(); it++)
		if(u->getDistance(*it) < distance)
			distance = u->getDistance(*it), perto = *it;
	return std::make_pair(perto, distance);
}

MineChooser MineChooser;
void AITrabalhador (Unidade* u){
	MineChooser.AssignMine(u);
}

Unidade* getAliadoDoTipo(BWAPI::UnitType tipo, bool completo = true){
	Unidade* unidade = NULL;
	std::set<Unidade*> aliados = base->getAllyUnits();
	for(std::set<Unidade*>::iterator it = aliados.begin(); it != aliados.end(); it++){
		if((*it)->getType() == tipo  && (*it)->isCompleted() == completo){
			unidade = *it;
		}
	}
	return unidade;
}

int contarUnidadesDoTipo(std::set<Unidade*> lista, BWAPI::UnitType tipo){
	int qtd = 0;
	for(std::set<Unidade*>::iterator it = lista.begin(); it != lista.end(); it++){
		if((*it)->getType() == tipo && (*it) -> exists()) ++qtd;
	}
	return qtd;
}

void construirPredio(Unidade* u, BWAPI::UnitType predio, int adi, Unidade* ref = NULL){
	PlaceChooser.MakeConstruction(u, predio);
	return;

	BWAPI::TilePosition tp = u->getTilePosition();
	int limite = 0;


	if(ref != NULL){
		tp = ref->getTilePosition();
	}
	
	while(!u->build(tp,predio)){
		if(rand() & 1) 
			tp = BWAPI::TilePosition(tp.x(), tp.y()+adi);
		else
			tp = BWAPI::TilePosition(tp.x()+adi, tp.y());
		tp.makeValid();
		limite++;
		if(limite > 50) break;
		adi = -adi + (adi > 0 ? -2 : +2);
	}
}

void AICentroComando (Unidade* u){
	if(u->getType() == BWAPI::UnitTypes::Protoss_Gateway)
		u->train(BWAPI::UnitTypes::Protoss_Zealot);
	else
		if(u->supplyTotal() - u->supplyUsed() >= BWAPI::UnitTypes::Protoss_Probe.supplyRequired()){
			if(u->minerals() > BWAPI::UnitTypes::Protoss_Probe.mineralPrice() && contarUnidadesDoTipo(u->getAllyUnits(), PROBE) < PROBES_LEGION_SIZE){
				u->train(BWAPI::UnitTypes::Protoss_Probe);
			}
		}
}

void AIForge(Unidade* u){
	if(u->gas() < UPGRADE_PLASMA_SHIELDS.gasPrice() || u->minerals() < UPGRADE_PLASMA_SHIELDS.mineralPrice()){
		Sleep(60);
		return;
	}
	u->upgrade(UPGRADE_PLASMA_SHIELDS);
}

DWORD WINAPI threadAgente(LPVOID param){	


	Unidade *u = (Unidade*) param;
	while(true){
		if(GameOver || u == NULL || !u->exists()) return 0;

		if(!u->isCompleted()){
			Sleep(500);
			continue;
		}
		if(!u->checkNovoTurno()){
			Sleep(10);
			continue;
		}

		else if(u->isIdle()){
			if(u->getType().isWorker()) AITrabalhador(u);
			else if(u->getType().canProduce()) AICentroComando(u);
			else if(u->getType() == FORGE) AIForge(u);
		}
		Sleep(10);
	}
}

DWORD WINAPI threadLider(LPVOID param) {
	int cont = 0;

	while(!GameOver) {
		Sleep(80);
		if(base == NULL) continue;
		MineChooser.Init(base);
		PlaceChooser.Init(base, 3);

		if(MineChooser.DistanciasCalculadas && cont){
			cont = 1;
			set<Unidade*> minas = base -> getMinerals();

			for(SETI it = minas.begin(); it != minas.end(); ++it){
				char buf[100];
				sprintf(buf, "BWAPI %lf, Meu %lf\n", base->getPosition().getDistance((*it)->getPosition()), MineChooser.DistanciaParaMinas[*it]);
				print(buf);
			}
		}

		int qtdZealotsInimigos = contarUnidadesDoTipo(base->getEnemyUnits(), ZEALOT);
		int qtdZealotsAliados = contarUnidadesDoTipo(base->getAllyUnits(), ZEALOT);

		if(qtdZealotsAliados - qtdZealotsInimigos >= 2){
			Sparta = true;
		}
	}
	return 0;
}

DWORD WINAPI updatePlaceChooser(LPVOID param){
	while(!GameOver)
		PlaceChooser.Update(base->getAllyUnits());
	return 0;
}

DWORD WINAPI threadConstrutora(LPVOID param) {
	Unidade* construtor = NULL, *assimilator = NULL;
	while(!GameOver){
		Sleep(30);

		if(construtor == NULL || !construtor->exists())
			construtor = getAliadoDoTipo(PROBE);

		if(construtor == NULL)continue;

		if(construtor->supplyTotal() - construtor->supplyUsed() <= 4 && getAliadoDoTipo(PYLON, false) == NULL){
			if(construtor->minerals() < PYLON_MINERAL_COST) continue;
			construirPredio(construtor, PYLON, 6);
		}
		else if(contarUnidadesDoTipo(construtor->getAllyUnits(), GATEWAY) < 2){
			if(construtor->minerals() < GATEWAY_MINERAL_COST) continue;
			Unidade* pylon = getAliadoDoTipo(PYLON);
			if(pylon != NULL) construirPredio(construtor, GATEWAY, 2, pylon);
		}
		else if(contarUnidadesDoTipo(construtor->getAllyUnits(), ASSIMILATOR) == 0){
			if(construtor->minerals() < ASSIMILATOR_MINERAL_COST || base == NULL) continue;
			Unidade* minaGas = getUnidadeMaisProxima(base, construtor->getGeysers()).first;
			construirPredio(construtor, ASSIMILATOR, 0, minaGas);
		}
		else if(contarUnidadesDoTipo(construtor->getAllyUnits(), FORGE) == 0){
			if(construtor->minerals() < FORGE_MINERAL_COST) continue;
			Unidade* pylon = getAliadoDoTipo(PYLON);
			if(pylon != NULL) construirPredio(construtor, FORGE, 2, pylon);
		}

		if((assimilator = getAliadoDoTipo(ASSIMILATOR)) != NULL){
			// 3 probes vao pegar gas
			int probesColetandoGas = 0;
			std::set<Unidade*> aliados = construtor->getAllyUnits();
			for(std::set<Unidade*>::iterator it = aliados.begin(); it != aliados.end(); ++it)
				if((*it)->getType() == PROBE && (*it)->isGatheringGas()) ++probesColetandoGas;

			int falta = GAS_COLLECTORS_MAX - probesColetandoGas;
			for(std::set<Unidade*>::iterator it = aliados.begin(); falta > 0 && it != aliados.end(); ++it)
				if((*it)->getType() == PROBE && !(*it)->isGatheringGas()) (*it)->rightClick(assimilator), --falta;
		}
	}

	return 0;
}

int contarGuerreirosTerrestres(set<Unidade*> lista){
	int qtd = 0;
	for(std::set<Unidade*>::iterator it = lista.begin(); it != lista.end(); ++it){
		if(((*it)->getType() == ZEALOT || (*it)->getType() == DRAGOON) && (*it)->isCompleted()){
			++qtd;
		}
	}
	return qtd;
}

Unidade* getAlvo(Unidade* atacante){
	std::set<Unidade*> inimigos = atacante->getEnemyUnits();
	Unidade *alvo = NULL;
	int qtdGuerreirosTerrestres = contarGuerreirosTerrestres(inimigos);
	int qtdPylons = contarUnidadesDoTipo(inimigos, PYLON);
	for(std::set<Unidade*>::iterator it = inimigos.begin(); it != inimigos.end(); ++it){
		if(qtdGuerreirosTerrestres > 0){
			if((*it)->getType() == ZEALOT || (*it)->getType() == DRAGOON){
				alvo = (*it);
				break;
			}
		}
		else if(qtdPylons > 0){
			if((*it)->getType() == PYLON){
				alvo = (*it);
				break;
			}
		}
		else{
			if((*it)->getType().isBuilding()){
				alvo = (*it);
				break;
			}
		}
	}
	return alvo;
}

Unidade* getGrupoTaticoEmMelhorCondicao(){
	Unidade *retorno = NULL;
	if(gruposTaticos.size() <= 1){
		return base;
	}
	int qtdMembros = 0;
	for(set<set<Unidade*>*>::iterator it = gruposTaticos.begin(); it != gruposTaticos.end(); ++it){
		if((*it)->size() > qtdMembros){
			retorno = *(*it)->begin();
			qtdMembros = (*it)->size();
		}
	}
	return retorno;
}

int getQuantidadeDeUnidadesNoRange(Unidade* ref, set<Unidade*> lista, int range){
	int qtd = 0;
	
	if(ref == NULL)return qtd;

	for(std::set<Unidade*>::iterator it = lista.begin(); it != lista.end(); ++it){
		if(ref->getDistance(*it) <= range && (*it)->getType() == ZEALOT){
			++qtd;
		}
	}
	return qtd;
}

Unidade* getGrupoTaticoEmApuros(){
	Unidade *lider = NULL;
	for(map<set<Unidade*>*,bool>::iterator it = emApuros.begin(); it != emApuros.end(); ++it){
		if((*it).second){
			lider = *(*it).first->begin();
			break;
		}
	}
	return lider;
}

DWORD WINAPI agenteTatico(LPVOID param) {
	std::set<Unidade*> *grupoTatico = (std::set<Unidade*>*) param;
	Unidade* alvo = NULL;

	while(!GameOver){		
		char debug[100];

		for(std::set<Unidade*>::iterator it = grupoTatico->begin(); it != grupoTatico->end(); ++it){
			if((*it) == NULL || !(*it)->exists()){
				grupoTatico->erase(it);
			}
		}
		
		if(grupoTatico->size() == 0){
			gruposTaticos.erase(grupoTatico);
			emApuros.erase(grupoTatico);
			return 0;
		}

		Unidade *lider = *grupoTatico->begin();
		int qtdInimigos = getQuantidadeDeUnidadesNoRange(lider, lider->getEnemyUnits(), 180);
		int qtdAliados = getQuantidadeDeUnidadesNoRange(lider, lider->getAllyUnits(), 180);

		//sprintf(debug, "INIMIGOS: %d - ALIADOS: %d\n", qtdInimigos, qtdAliados);
		//print(debug);

		if(qtdAliados < grupoTatico->size() && lider != NULL){
			print("reagrupando\n");
			for(std::set<Unidade*>::iterator it = grupoTatico->begin(); it != grupoTatico->end(); ++it){
				(*it)->follow(lider);
			}
			lider->stop();
			Sleep(20);
			continue;
		}

		Unidade *liderDoOutroGrupo;
		if(qtdInimigos > qtdAliados){
			liderDoOutroGrupo = getGrupoTaticoEmMelhorCondicao();

			if(liderDoOutroGrupo != NULL){
				for(std::set<Unidade*>::iterator it = grupoTatico->begin(); it != grupoTatico->end(); ++it){
					(*it)->follow(liderDoOutroGrupo);
				}
			}
			emApuros[grupoTatico] = true;
		}
		else if(emApuros[grupoTatico]){
			liderDoOutroGrupo = getGrupoTaticoEmMelhorCondicao();

			if(lider != NULL && liderDoOutroGrupo!= NULL && lider->getDistance(liderDoOutroGrupo)){
				emApuros[grupoTatico] = false;
				for(std::set<Unidade*>::iterator it = grupoTatico->begin(); it != grupoTatico->end(); ++it){
					(*it)->stop();
				}
			}
		}
		else{
			if((liderDoOutroGrupo = getGrupoTaticoEmApuros()) != NULL){
				for(std::set<Unidade*>::iterator it = grupoTatico->begin(); it != grupoTatico->end(); ++it){
					(*it)->attack(liderDoOutroGrupo->getPosition());
				}
			}
			else if(lider != NULL && lider->isIdle()){
				set<Unidade*> inimigos = lider->getEnemyUnits();
				print("escolhendo alvo\n");
				if(inimigos.size() > 0){
					alvo = getAlvo(lider);
					if(alvo != NULL){
						for(std::set<Unidade*>::iterator it = grupoTatico->begin(); it != grupoTatico->end(); ++it){
							if(alvo->getType().isBuilding()){
								(*it)->attack(alvo);
							}
							else{
								(*it)->attack(alvo->getPosition());
							}
						}
					}
				}
			}
		}

		Sleep(20);
	}
	return 0;
}

void MeuAgentePrincipal::InicioDePartida(){
	if(DEBUG){
		AllocConsole();
		teste = GetStdHandle(STD_OUTPUT_HANDLE);
	}

	GameOver = false;
	NewThread(updatePlaceChooser, NULL);
	CreateThread(NULL,0,threadLider,NULL,0,NULL);
	CreateThread(NULL,0,threadConstrutora,NULL,0,NULL);
}

void MeuAgentePrincipal::onEnd(bool isWinner){
	GameOver = true;
	Sleep(550);
	if(DEBUG) FreeConsole();
}

void MeuAgentePrincipal::UnidadeCriada(Unidade* unidade){
	BWAPI::UnitType tipo = unidade->getType();

	if(tipo == BWAPI::UnitTypes::Protoss_Nexus){
		base = unidade;
	}

	if(tipo == ZEALOT){
		while(unidade != NULL && !unidade->isCompleted()) Sleep(20);
		if(grupoTaticoAtual == NULL) grupoTaticoAtual = new set<Unidade*>();
		
		grupoTaticoAtual->insert(unidade);

		if(grupoTaticoAtual->size() >= 5){
			NewThread(agenteTatico, (void*)grupoTaticoAtual);
			emApuros[grupoTaticoAtual] = false;
			gruposTaticos.insert(grupoTaticoAtual);
			grupoTaticoAtual = NULL;
		}
	}
	else if(tipo != PYLON){
		CreateThread(NULL,0,threadAgente,(void*)unidade,0,NULL);
	}
}
